﻿Imports System.Data.Caching
Public NotInheritable Class CachingScope
    Implements IDisposable
#Region "Properties"
    ''' <summary>
    ''' Contains the array of cached items to clear when the scope is disposed
    ''' </summary>
    ''' <remarks></remarks>
    Private CacheArray As Dictionary(Of String, Object)
    ''' <summary>
    ''' Contains the array fo cached objects to pass tothe user when they make a read request on a locked object.
    ''' </summary>
    ''' <remarks></remarks>
    Private CacheObjectArray As Dictionary(Of String, Object)

    ''' <summary>
    ''' Indicates tha the current scope is active and accepting locks in a transaction scenario
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared _ScopeActive As Boolean
    ''' <summary>
    ''' Indicates tha the current scope is active and accepting locks in a transaction scenario
    ''' </summary>
    ''' <remarks></remarks>
    Shared Property ScopeActive() As Boolean
        Get
            Return _ScopeActive
        End Get
        Protected Set(ByVal value As Boolean)
            _ScopeActive = value
        End Set
    End Property

    Public Shared _LockHandle As LockHandle
    Private CurrentParentLockHandle As LockHandle

    Private Shared CurrentScope As CachingScope
    Private ParentScopeObj As CachingScope

    ''' <summary>
    ''' Indicates if this scope is a parent and has the authority to clear the cache and unlock its objects
    ''' </summary>
    ''' <remarks></remarks>
    Private ParentScope As Boolean
    ''' <summary>
    ''' Specifies the type of scope to create
    ''' </summary>
    ''' <remarks></remarks>
    Public Enum ScopeTypeEnum
        ''' <summary>
        ''' Adds this scope to any pre-existing scope or creates a new scope if none exists.
        ''' </summary>
        ''' <remarks></remarks>
        Enlist = 0
        ''' <summary>
        ''' Forces a new scope to be created adn caching set seperately on it.
        ''' </summary>
        ''' <remarks></remarks>
        RequiresNew = 1
        ''' <summary>
        ''' Suppresses caching for all children unless a ReqiresNew parameter is set on a child scope.
        ''' </summary>
        ''' <remarks></remarks>
        Suppress = 2
    End Enum
    ''' <summary>
    ''' Holds the ID Value of the lowest possivle scope instance
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared _ScopeID As Int32
    Shared Property ScopeID() As Int32
        Get
            Return _ScopeID
        End Get
        Private Set(ByVal value As Int32)
            _ScopeID = value
        End Set
    End Property
    ''' <summary>
    ''' Holds the ID Value of the Lowest Parent scope.
    ''' </summary>
    ''' <remarks>This is the active set of scopes that must be committed befor ethe parents of it can be committed</remarks>
    Private Shared ParentScopeID As Int32
    ''' <summary>
    ''' Holds the value of the parent of this instance when it was created.
    ''' </summary>
    ''' <remarks>this value will be reinstated when the cache for this scope is cleared.</remarks>
    Private CurrentParentScopeID As Int32
    ''' <summary>
    ''' Holds the value for the current scope instance
    ''' </summary>
    ''' <remarks></remarks>
    Private CurrentScopeID As Int32
    Private ScopeType As ScopeTypeEnum
#End Region
    ''' <summary>
    ''' Creates a new scope if none is active and sets the scope as active
    ''' </summary>
    ''' <remarks></remarks>
    Sub New(Optional ByVal ScopeType As ScopeTypeEnum = ScopeTypeEnum.Enlist)
        ParentScopeObj = CurrentScope
        Me.ScopeType = ScopeType

        ScopeID += 1 'Incriments the Global Scope
        CurrentScopeID = ScopeID 'Sets the value of the current scope
        CurrentParentScopeID = ParentScopeID 'record what the current parent scope is
        ScopeActive = True 'Sets the current scope as active for accepting loacked items
        CurrentParentLockHandle = _LockHandle
        If CacheArray Is Nothing Then
            CacheArray = New Dictionary(Of String, Object)
            CacheObjectArray = New Dictionary(Of String, Object)
        End If

        Select Case ScopeType
            Case ScopeTypeEnum.Enlist
                If ScopeID = 1 Then
                    ParentScope = True
                    ParentScopeID = ScopeID
                    _LockHandle = New LockHandle
                End If
            Case ScopeTypeEnum.RequiresNew
                ParentScope = True
                ParentScopeID = ScopeID
                _LockHandle = New LockHandle
            Case ScopeTypeEnum.Suppress
                ParentScope = True
                ParentScopeID = ScopeID
                ScopeActive = False
                _LockHandle = New LockHandle
        End Select

        CurrentScope = Me
    End Sub
    ''' <summary>
    ''' Returns an object if it has already been cached and locked.
    ''' </summary>
    ''' <param name="Region"></param>
    ''' <param name="Key"></param>
    ''' <returns>The object if it is found and nothing if the object is not found.</returns>
    ''' <remarks></remarks>
    Shared Function CheckandGetCachedItem(ByVal Region, ByVal Key) As Object
        If CurrentScope.CacheArray.ContainsKey(Region & "_" & Key) Then
            Dim CA As Object() = CurrentScope.CacheArray.Item(Region & "_" & Key) 'The Cache array object
            If CA(4) = ParentScopeID Then
                Return CurrentScope.CacheObjectArray.Item(Region & "_" & Key)
            Else
                Return Nothing
            End If
        Else
            Return Nothing
        End If
    End Function
    Shared Sub AddItemToCacheArray(ByVal CacheName As CachingService.CacheListEnum, ByVal Region As String, ByVal Key As String, ByVal Lock As Caching.LockHandle, ByVal Value As Object)
        If Not ScopeActive Then
            Throw New Exception("You can only lock cached items within the context of a and active CachingScope.")
        End If
        Dim CA(4) As Object
        CA(0) = CacheName
        CA(1) = Region
        CA(2) = Key
        CA(3) = _LockHandle
        CA(4) = ParentScopeID
        If Not CurrentScope.CacheArray.ContainsKey(Region & "_" & Key) Then
            CurrentScope.CacheArray.Add(Region & "_" & Key, CA)
            CurrentScope.CacheObjectArray.Add(Region & "_" & Key, Value)
        Else
            CurrentScope.CacheArray.Item(Region & "_" & Key) = CA
            CurrentScope.CacheObjectArray.Item(Region & "_" & Key) = Value
        End If
    End Sub
    Private Sub ClearCacheArray()
        Dim TempCA As New ArrayList
        Dim CA As Object() 'The Cache array object
        If Not CacheArray Is Nothing Then
            For Each item As System.Collections.Generic.KeyValuePair(Of String, Object) In CacheArray
                CA = item.Value
                If CA(4) = ScopeID Then
                    CachingService.MyCache(CType(CA(0), CachingService.CacheListEnum)).Unlock(CType(CA(1), String), CType(CA(2), String), CType(CA(3), LockHandle))
                    TempCA.Add(item.Key)
                End If
            Next
            For Each item As String In TempCA
                CacheArray.Remove(item)
                CacheObjectArray.Remove(item)
            Next
            TempCA.Clear()
        End If
    End Sub
    Private Sub MigrateCacheArray()
        If Not CacheArray Is Nothing Then
            For Each item As System.Collections.Generic.KeyValuePair(Of String, Object) In CacheArray
                ParentScopeObj.CacheArray.Add(item.Key, item.Value)
            Next
            For Each item As System.Collections.Generic.KeyValuePair(Of String, Object) In CacheObjectArray
                ParentScopeObj.CacheObjectArray.Add(item.Key, item.Value)
            Next
        End If
    End Sub
    Private disposedValue As Boolean = False        ' To detect redundant calls
    ' IDisposable

    'Shared ReadOnly padlock As New Object
    Protected Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue And ParentScope Then
            If disposing Then
                ' TODO: free other state (managed objects).
            End If

            ' TODO: free your own state (unmanaged objects).
            If ParentScope = True Then
                ClearCacheArray()
                ParentScopeID = CurrentParentScopeID
                _LockHandle = CurrentParentLockHandle
            Else
                MigrateCacheArray()
            End If
            ScopeID -= 1
            If ScopeID = 0 Then
                CurrentScope = Nothing
                ScopeActive = False
            Else
                CurrentScope = ParentScopeObj
                Select Case ScopeType
                    Case ScopeTypeEnum.Enlist
                        ScopeActive = True
                    Case ScopeTypeEnum.RequiresNew
                        ScopeActive = True
                    Case ScopeTypeEnum.Suppress
                        ScopeActive = False
                End Select
            End If

            CacheArray.Clear()
            CacheObjectArray.Clear()

            CacheArray = Nothing
            CacheObjectArray = Nothing

            CurrentParentLockHandle = Nothing
            ParentScopeObj = Nothing
            ParentScope = Nothing
            CurrentParentScopeID = Nothing
            CurrentScopeID = Nothing
            ScopeType = Nothing

            If ScopeID = 0 Then
                _ScopeActive = Nothing
                _LockHandle = Nothing
                CurrentScope = Nothing
                ScopeID = Nothing
                ParentScopeID = Nothing
            End If
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub
#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region
End Class